#include <gtkmm.h>
#include <libglademm/xml.h>
#include <iostream>
//#include <sstream>
//#include <string>
#include <cairomm/context.h>
#include "include/daq.h"

/** \todo TODO
* - allow adjustments of capture parameters and display parameters
* - allow adjustment of data buffer size (based on capture parameters
* - scale line width to yscale factor
* - check test return statements, something weird may be going on
*/

/* options to add */
double Gfrequency = 10000.0;
int Gnchannels = 2;
int Gnsamples = 100;
bool Gphysical = true;
bool Gverbose = false;
bool Gready = false;

// TODO the data is not continuous, is it even worthwhile to save?
//bool GprintData = false;

//linecolors =
// what are the units of GyScale?
double GyScale = 10.0;
int Gtimeout = 10;

// TODO colors!!!!!!!!!!

//void some_handler(GtkWidget *widget) {
//	/* a handler referenced by the glade file.  Must not be static
//	* so that it appears in the global symbol table. */
//}

const int nVolatileSpins = 5;
char volatileSpins[nVolatileSpins][32] = {
	"frequencySpin",
	"nChannelsSpin",
	"nSamplesSpin",
	"yScaleSpin",
	"timeoutSpin" };

const int nVolatileCheckButtons = 2;
char volatileCheckButtons[nVolatileCheckButtons][32] = {
	"verboseCheck",
	"physicalCheck" };

class GraphDaq : public Daq {
	public:
		GraphDaq(char* filename = D_FILENAME);
		//double data[4][1000];
		double** daqData;
		int index;
		void print_data();
		bool free_data();
		bool allocate_data();
	protected:
		virtual void process_physical_datum(double physical_value, int channel);
		virtual void process_raw_datum(double raw_value, int channel);
};

GraphDaq::GraphDaq(char* filename)
 : Daq(filename) {
	index = 0;
	daqData = NULL;
}

bool GraphDaq::free_data() {
	if (daqData == NULL) {
		std::cerr << "GraphDaq:: method free_data() was called when no data array was present\n";
		return 1;
	}
	for (int c = 0; c < nChannels; c++) {
		delete [] daqData[c];
	}
	delete [] daqData;
	daqData = NULL;
	return 0;
}

bool GraphDaq::allocate_data() {
	if (daqData != NULL) {
		std::cerr << "GraphDaq:: allocate_data() was called when data array was present, FATAL ERROR?\n";
		return 1;
	}
	daqData = new double*[nChannels];
	for (int c = 0; c < nChannels; c++) {
		daqData[c] = new double[nSamples];
		for (int i = 0; i < nSamples; i++) {
			daqData[c][i] = 0.0;
		}
	}
	index = 0;
	return 0;
}

void GraphDaq::print_data(){
	for (int i = 0; i < nSamples; i++){
		for (int c = 0; c < nChannels; c++){
			std::cout << daqData[c][i] << " ";
		}
		std::cout << "\n";
	}
}

void GraphDaq::process_physical_datum(double physical_value, int channel) {
	daqData[channel][index] = physical_value;
	//std::cout << "chan: " << channel << " nchan: " << nChannels;
	if (channel == nChannels - 1) {
		//std::cout << " incrementing index!!!!!!!!!!!!!!!!!";
		index++;
		if (index >= nSamples) {
			index = 0;
		}
	}
	//std::cout << "\n";
}

void GraphDaq::process_raw_datum(double raw_value, int channel) {
	process_physical_datum(raw_value, channel);
//	data[channel][index] = physical_value;
//	if (channel = nChannels - 1) {
//		index++;
//		if (index >= 1000) {
//			index = 0;
//		}
//	}
}

class GraphDrawingArea : public Gtk::DrawingArea {
	public:
		GraphDrawingArea(BaseObjectType* cobject, Glib::RefPtr<Gnome::Glade::Xml> gladeXml);
		virtual ~GraphDrawingArea();
		GraphDaq* pDaq;
		Gtk::Label* pLabel;
		bool force_redraw();
	protected:
		virtual bool on_expose_event(GdkEventExpose* event);
};

GraphDrawingArea::GraphDrawingArea(BaseObjectType* cobject, Glib::RefPtr<Gnome::Glade::Xml> gladeXml)
 : Gtk::DrawingArea(cobject) {
 	gladeXml->get_widget("label1",pLabel);
}

GraphDrawingArea::~GraphDrawingArea(){
}

bool GraphDrawingArea::force_redraw(){
	// force our program to redraw
	Glib::RefPtr<Gdk::Window> win = get_window();
	if (win) {
		Gdk::Rectangle r(0, 0, get_allocation().get_width(),get_allocation().get_height());
		win->invalidate_rect(r, false);
	}
	return true;
}

bool GraphDrawingArea::on_expose_event(GdkEventExpose* event) {
  // This is where we draw on the window
  Glib::RefPtr<Gdk::Window> window = get_window();
  if(window)
  {
    Gtk::Allocation allocation = get_allocation();
    const int width = allocation.get_width();
    const int height = allocation.get_height();

    // coordinates for the center of the window
    int xc, yc;
    xc = width / 2;
    yc = height / 2;

    Cairo::RefPtr<Cairo::Context> cr = window->create_cairo_context();
    //cr->set_line_width(1.0);

    // clip to the area indicated by the expose event so that we only redraw
    // the portion of the window that needs to be redrawn
    cr->rectangle(event->area.x, event->area.y,
            event->area.width, event->area.height);
    cr->clip();

	cr->save();
	cr->set_source_rgb(0.0,0.0,0.0);
	cr->paint();
	cr->restore();

	// grid?
	cr->save();
	cr->scale(width, height); // makes the canvas x:0->1.0, y:0->1.0
	cr->set_source_rgb(0.5, 0.5, 0.5); // makes pen a light gray
	cr->set_line_width(0.001);
	std::valarray<double> dashes(1);
	dashes[0] = 0.01;
	cr->set_dash(dashes,0);
	// draw horizontals
	cr->move_to(0.0,0.1); cr->line_to(1.0,0.1);
	cr->move_to(0.0,0.2); cr->line_to(1.0,0.2);
	cr->move_to(0.0,0.3); cr->line_to(1.0,0.3);
	cr->move_to(0.0,0.4); cr->line_to(1.0,0.4);
	cr->move_to(0.0,0.5); cr->line_to(1.0,0.5);
	cr->move_to(0.0,0.6); cr->line_to(1.0,0.6);
	cr->move_to(0.0,0.7); cr->line_to(1.0,0.7);
	cr->move_to(0.0,0.8); cr->line_to(1.0,0.8);
	cr->move_to(0.0,0.9); cr->line_to(1.0,0.9);
	// draw verticals
	cr->move_to(0.1,0.0); cr->line_to(0.1,1.0);
	cr->move_to(0.2,0.0); cr->line_to(0.2,1.0);
	cr->move_to(0.3,0.0); cr->line_to(0.3,1.0);
	cr->move_to(0.4,0.0); cr->line_to(0.4,1.0);
	cr->move_to(0.5,0.0); cr->line_to(0.5,1.0);
	cr->move_to(0.6,0.0); cr->line_to(0.6,1.0);
	cr->move_to(0.7,0.0); cr->line_to(0.7,1.0);
	cr->move_to(0.8,0.0); cr->line_to(0.8,1.0);
	cr->move_to(0.9,0.0); cr->line_to(0.9,1.0);
	cr->stroke();
	cr->restore();
//	Glib::ustring labelText;
	//std::stringstream
	//using Glib::ustring;
	//ustring labelText = ustring::compose("dX: %1 ms | dY: %2 Volts",Gnsamples / 10.0 * 1000.0 / Gfrequency,0.2 * GyScale);
	

//	using Glib::ustring;
//	ustring message = Glib::ustring::compose("%1 is lower than 0x%2.",12, ustring::format(std::hex, 16));
//	labelText << "dX : " << Gnsamples / 10.0 * 1000.0 / Gfrequency << " ms dY : " << 0.2 * GyScale << " Volts";

	//pLabel->set_text("dX : " + Gnsamples / 10.0 * 1000.0 / Gfrequency + " ms dY : " + 0.2 * GyScale + " Volts");
	//std::cout << "dX : " << Gnsamples / 10.0 * 1000.0 / Gfrequency << " ms dY : " << 0.2 * GyScale << " Volts\n";


	//cr->scale(width/1000.0, height/2.0);
	//float yScale = 5.0;
	cr->translate(0.0, height/2);
	cr->scale(width/float(Gnsamples), height/(2.0*GyScale));
	//cr->set_line_width(0.01);
	cr->set_line_width(0.01 * GyScale);
	
//	// ------------------- draw scale & info, how do I do text? ----------------------------
//	cr->save();
//	cr->set_source_rgb(1.0,1.0,1.0);
//	cr->set_line_width(0.01 * GyScale);
//	cr->move_to(Gnsamples * 0.5, 0.5 * GyScale);
//	// calculate 1 ms or microsecond?
//	// Gfrequency/1000.0 = samples per millisecond
//	// draw scale 10 ms by 10 mV
//	// Gnsamples / 10.0 -> number of samples per 0.1 of graph
//	// 
//	//cr->line_to(Gnsamples * 0.5 + Gfrequency/1000.0 * 10.0, 0.5 * GyScale);
//	cr->line_to(Gnsamples * 0.5 + Gnsamples/ 10.0, 0.5 * GyScale);
//	cr->stroke();
//	cr->set_line_width(0.004 * Gnsamples);
////	cr->move_to(Gnsamples * 0.5, 0.5 * GyScale);
////	cr->line_to(Gnsamples * 0.5, 0.5 * GyScale - 1);
//	cr->move_to(Gnsamples * 0.5, 0.5 * GyScale);
//	//cr->line_to(Gnsamples * 0.5, -1); // this goes 'up' one volt
//	//cr->line_to(Gnsamples * 0.5, -0.1); // this goes 'up' 100 millivolts
//	cr->line_to(Gnsamples * 0.5, 0.5 * GyScale - GyScale / 5.0);
//	std::cout << "dX : " << Gnsamples / 10.0 * 1000.0 / Gfrequency << " ms dY : " << 0.2 * GyScale << " Volts\n";
//	// GyScale = 1.0, -0.1
//	// 1 unit (from middle to top of graph) = 
//	// GyScale is in volts per half-height of graph
//	// if I go 'up' 0.1 units, I went up 0.1 * GyScale volts
//	// how do I figure out what 1/10 of GyScale 
//	// 1000/Gfrequency
//	//std::cout << Gnsamples / Gfrequency * 100.0 << "ms per 10 samples\n";
////	Gfrequency/1000.0
////	cr->line_to(
//	cr->stroke();
//	cr->restore();
//	// ----------------------------- end scale ------------------------
	
//	// ------------------- draw baseline (TODO make this a grid) ---------------------
//	std::valarray<double> dashes(1,1);
//	cr->save();
//	cr->set_dash(dashes,0);
//	cr->set_source_rgb(0.9,0.9,0.9);
//	cr->move_to(0,0);
//	cr->line_to(Gnsamples,0);
//	cr->stroke();
//	cr->restore();
//	std::valarray<double> nodashes;
//	cr->set_dash(nodashes,0);
//	// ---------------------- end baseline ------------------------------
	
    // draw red lines out from the center of the window
		for (int c = 0; c < Gnchannels; c++) {
			if (c == 0) cr->set_source_rgb(1.0, 0.0, 0.0);
			else if (c == 1) cr->set_source_rgb(0.0, 1.0, 0.0);
			else if (c == 2) cr->set_source_rgb(0.0, 0.0, 1.0);
			else if (c == 3) cr->set_source_rgb(0.0, 1.0, 1.0);
			else cr->set_source_rgb(1.0, 0.0, 1.0);
			cr->move_to(0,0);
			for (int i = 0; i < Gnsamples; i++) {
				cr->line_to(double(i),-pDaq->daqData[c][i]);
				//cr->move_to(double(i),pDaq->data[c][i]+100);
			}
			cr->stroke();
		}
	//cr->stroke();
//    cr->set_source_rgb(0.8, 0.0, 0.0);
//    cr->move_to(0, 0);
//    cr->line_to(xc, yc);
//    cr->line_to(0, height);
//    cr->move_to(xc, yc);
//    cr->line_to(width, yc);
//    cr->stroke();
  }

  return true;
}

GraphDaq* daq;
GraphDrawingArea* pArea;
Glib::RefPtr<Gnome::Glade::Xml> gladeXml;
Gtk::ToggleButton* pToggle;
Gtk::Window* pOptionsWindow;
Gtk::Label* pTestReadyLabel;
//char labelText[50];
sigc::connection timer;

bool exit_callback(GdkEventAny* event) {
	Gtk::Main::quit();
	return true;
}

bool capture_loop() {
	daq->index = 0;
	daq->start_recording();
	pArea->force_redraw();
	if (pToggle->get_active()) {
		//std::cout << timer << "\n";
		timer = Glib::signal_timeout().connect(sigc::ptr_fun(&capture_loop), Gtimeout);
		//timer = Glib::SignalTimeout::connect(sigc::ptr_fun(&capture_loop), 1000);
		//Glib::SignalTimeout::connect(const sigc::slot<bool>& slot, unsigned int interval, int priority = Glib::PRIORITY_DEFAULT);
		// assign a 'time-out' function to recall capture_loop in such and such a time
	}
	// returning false kills this timeout, but don't worry, another will occur if need be
	return false;
}

void toggle_callback() {
	if (pToggle->get_active()){
		if (Gready) {
			pToggle->set_label("Stop Capturing");
			capture_loop();
		} // attempted 'go' when not ready
	} else {
		pToggle->set_label("Start Capturing");
	}
//	//std::cout << "captured new data\n";
//	daq->start_recording();
//	//daq->print_data();
//	pArea->force_redraw();
}

void update_main_label() {
	Gtk::Label* pLabel;
	//std::ostringstream temp;
	gladeXml->get_widget("label1", pLabel);
	char labelText[50];
	//std::cout << "updating main label\n";
	snprintf(labelText,49, "dX : %4.4f ms | dY : %4.4f Volts", Gnsamples / 10.0 * 1000.0 / Gfrequency, 0.2 * GyScale);
	pLabel->set_text(labelText);
}

void update_optionsWindow() {
	Gtk::SpinButton* pSpin;
	Gtk::CheckButton* pCheckButton;
	// update frequency
	gladeXml->get_widget("frequencySpin", pSpin);
	//std::cout << "setting frequencySpin value to " << Gfrequency << "\n";
	pSpin->set_value(Gfrequency);
	//std::cout << "\t frequencySpin value is: " << pSpin->get_value() << "\n";
	// update nsamples
	gladeXml->get_widget("nSamplesSpin", pSpin);
	pSpin->set_value(Gnsamples);
	// update nchannels
	gladeXml->get_widget("nChannelsSpin", pSpin);
	pSpin->set_value(Gnchannels);
	// update physical
	gladeXml->get_widget("physicalCheck", pCheckButton);
	pCheckButton->set_active(Gphysical);
	// update verbose
	gladeXml->get_widget("verboseCheck", pCheckButton);
	pCheckButton->set_active(Gverbose);
	// update yScale
	gladeXml->get_widget("yScaleSpin", pSpin);
	pSpin->set_value(GyScale);
	// update timeout
	gladeXml->get_widget("timeoutSpin", pSpin);
	pSpin->set_value(Gtimeout);
	// TODO update colors????

	// also, update the label on the main window:
	update_main_label();
}

void optionsButton_callback() {
	if (pOptionsWindow->is_visible()) {
		pOptionsWindow->hide();
	} else {
		update_optionsWindow();
		pOptionsWindow->show();
	}
}

void set_ready() {
	std::cout << "\t\tRead = TRUE\n";
	Gready = true;
	pTestReadyLabel->set_text("Ready");
	// TODO set background green
}

void set_not_ready() {
	std::cout << "\t\tReady = FALSE\n";
	if (pToggle->get_active()) {
		pToggle->set_active(false);
	};
	Gready = false;
	pTestReadyLabel->set_text("Not Ready");
	// TODO set background red
}

void test_current_command() {
	if (daq->daqData != NULL) {
		daq->free_data();
	}
	// get options from optionsWindow widgets
	Gtk::SpinButton* pSpin;
	Gtk::CheckButton* pCheckButton;	
	
	gladeXml->get_widget("frequencySpin", pSpin);
	//std::cout << "setting frequencySpin value to " << Gfrequency << "\n";
	Gfrequency = pSpin->get_value();
	//std::cout << "\t frequencySpin value is: " << pSpin->get_value() << "\n";
	// update nsamples
	gladeXml->get_widget("nSamplesSpin", pSpin);
	Gnsamples = (int)pSpin->get_value();
	// update nchannels
	gladeXml->get_widget("nChannelsSpin", pSpin);
	Gnchannels = (int)pSpin->get_value();
	// update physical
	gladeXml->get_widget("physicalCheck", pCheckButton);
	Gphysical = pCheckButton->get_active();
	// update verbose
	gladeXml->get_widget("verboseCheck", pCheckButton);
	Gverbose = pCheckButton->get_active();
	// update yScale
	gladeXml->get_widget("yScaleSpin", pSpin);
	GyScale = pSpin->get_value();
	// update timeout
	gladeXml->get_widget("timeoutSpin", pSpin);
	Gtimeout = (int)pSpin->get_value();
	// TODO colors
	
	// setup command
	daq->set_verbose(Gverbose);
	daq->set_physical(Gphysical);
	daq->set_nChannels(Gnchannels);
	daq->set_frequency(Gfrequency);
	daq->set_nSamples(Gnsamples);
	bool testFailed = true;
	int testIndex = 0;
	while (testFailed) {
		testIndex++;
		testFailed = daq->test_command();
		std::clog << "Test " << testIndex << " returned: " << testFailed << "\n";
		if (testIndex > 2) break;
	}
	if (testFailed) {
		std::cerr << "Tests failed, exiting program\n";
		set_not_ready();
		return;
	}
	std::clog << "Test succeeded, prepping data and command\n";
	// check what arguments 'passed' the test and fill those into the globals
	Gfrequency = daq->get_frequency();
	Gnchannels = daq->get_nChannels();
	Gnsamples = daq->get_nSamples();
	// put those updated values into the options window
	update_optionsWindow();
	// allocate data for upcoming capture
	daq->allocate_data();
	set_ready();
	// -- now ready for capturing --
}

int main(int argc, char **argv) {

	Gtk::Main kit(argc, argv);

	// load the interface from glade
	//std::cout << "loading gScope.glade\n";
	gladeXml = Gnome::Glade::Xml::create("gScope.glade");
	//std::cout << "gScope.glade loaded\n";

	// connect exit_callback to main window delete event
	Gtk::Window* pWindow = 0;
	gladeXml->get_widget("window1", pWindow);
	pWindow->signal_delete_event().connect( sigc::ptr_fun(&exit_callback) );

	// connect toggle button to callback
	//Gtk::ToggleButton* pToggle = 0;
	gladeXml->get_widget("togglebutton1", pToggle);
	pToggle->signal_toggled().connect( sigc::ptr_fun(&toggle_callback) );
	
	// connect options button to callback
	gladeXml->get_widget("optionsWindow", pOptionsWindow);
	gladeXml->get_widget("testReadyLabel", pTestReadyLabel);
	Gtk::Button* pButton;
	gladeXml->get_widget("optionsButton", pButton);
	pButton->signal_clicked().connect( sigc::ptr_fun(&optionsButton_callback) );
	
	// connect test command button to test_current_command
	gladeXml->get_widget("testCommandButton", pButton);
	pButton->signal_clicked().connect( sigc::ptr_fun(&test_current_command) );
	
	// connect all 'volatile' optionsWindow widgets to the set_ready(false) function so that when any option is changed, the program becomes un-ready
	Gtk::SpinButton* pSpin;
	for (int i=0; i < nVolatileSpins; i++) {
		gladeXml->get_widget(volatileSpins[i], pSpin);
		pSpin->signal_changed().connect( sigc::ptr_fun(&set_not_ready) );
	}
	Gtk::CheckButton* pCheckButton;
	for (int i=0; i < nVolatileCheckButtons; i++) {
		gladeXml->get_widget(volatileCheckButtons[i], pCheckButton);
		pCheckButton->signal_toggled().connect( sigc::ptr_fun(&set_not_ready) );
	}
	

//	Gtk::Label* pLabel;
//	//std::ostringstream temp;
//	gladeXml->get_widget("label1", pLabel);
//	char labelText[50];
//	snprintf(labelText,49, "dX : %4.4f ms | dY : %4.4f Volts", Gnsamples / 10.0 * 1000.0 / Gfrequency, 0.2 * GyScale);
//	//sprintf(labelText,"why is this breaking stuff? %4.4f",10.0f);
//	pLabel->set_text(labelText);
//	temp << "why is this breaking stuff?" << 2;
//	pLabel->set_text(temp.str());

	// draw some test crap on the drawing area
	//GraphDrawingArea* pArea;
	gladeXml->get_widget_derived("drawingarea1", pArea);
//	cr = pArea->get_window()->create_cairo_context();
//	pArea->signal_expose_event().connect( sigc::ptr_fun(&expose_callback) );
//	cr->set_source_rgb(0.337, 0.612, 0.117);
//	cr->paint();

	daq = new GraphDaq("/dev/comedi0");
	pArea->pDaq = daq;
	
	// update window options (with defaults)
	update_optionsWindow();
	// test command constructed from window options
	test_current_command();
	
	// start main loop (does not return until Gtk::Main::quit() is called
	Gtk::Main::run();
	if (timer) timer.disconnect();
	return 0;
}

